// ----------------------------------------------------------------------
//
// ComLogger.cpp
//
// ----------------------------------------------------------------------

#include <Fw/FPrimeBasicTypes.hpp>
#include <Fw/Types/SerialBuffer.hpp>
#include <Fw/Types/StringUtils.hpp>
#include <Os/ValidateFile.hpp>
#include <Svc/ComLogger/ComLogger.hpp>
#include <cstdio>

namespace Svc {
static_assert(std::numeric_limits<U16>::max() <= std::numeric_limits<FwSizeType>::max(),
              "U16 must fit in the positive range of FwSizeType");
// ----------------------------------------------------------------------
// Construction, initialization, and destruction
// ----------------------------------------------------------------------

ComLogger ::ComLogger(const char* compName, const char* incomingFilePrefix, U32 maxFileSize, bool storeBufferLength)
    : ComLoggerComponentBase(compName),
      m_maxFileSize(maxFileSize),
      m_fileMode(CLOSED),
      m_byteCount(0),
      m_writeErrorOccurred(false),
      m_openErrorOccurred(false),
      m_storeBufferLength(storeBufferLength),
      m_initialized(true) {
    this->init_log_file(incomingFilePrefix, maxFileSize, storeBufferLength);
}

ComLogger ::ComLogger(const char* compName)
    : ComLoggerComponentBase(compName),
      m_filePrefix(),
      m_maxFileSize(0),
      m_fileMode(CLOSED),
      m_fileName(),
      m_hashFileName(),
      m_byteCount(0),
      m_writeErrorOccurred(false),
      m_openErrorOccurred(false),
      m_storeBufferLength(),
      m_initialized(false) {}

void ComLogger ::init_log_file(const char* incomingFilePrefix, U32 maxFileSize, bool storeBufferLength) {
    FW_ASSERT(incomingFilePrefix != nullptr);
    this->m_maxFileSize = maxFileSize;
    this->m_storeBufferLength = storeBufferLength;
    if (this->m_storeBufferLength) {
        FW_ASSERT(maxFileSize > sizeof(U16), static_cast<FwAssertArgType>(maxFileSize));
    }
    // Assign the prefix checking if it is too big
    Fw::FormatStatus formatStatus = this->m_filePrefix.format("%s", incomingFilePrefix);
    FW_ASSERT(formatStatus == Fw::FormatStatus::SUCCESS);
    this->m_initialized = true;
}

ComLogger ::~ComLogger() {
    // Close file:
    // this->closeFile();
    // NOTE: the above did not work because we don't want to issue an event
    // in the destructor. This can cause "virtual method called" segmentation
    // faults.
    // So I am copying part of that function here.
    if (OPEN == this->m_fileMode) {
        // Close file:
        this->m_file.close();

        // Write out the hash file to disk:
        this->writeHashFile();

        // Update mode:
        this->m_fileMode = CLOSED;

        // Send event:
        // Fw::LogStringArg logStringArg((char*) fileName);
        // this->log_DIAGNOSTIC_FileClosed(logStringArg);
    }
}

// ----------------------------------------------------------------------
// Handler implementations
// ----------------------------------------------------------------------

void ComLogger ::comIn_handler(FwIndexType portNum, Fw::ComBuffer& data, U32 context) {
    FW_ASSERT(portNum == 0);

    // Get length of buffer:
    FwSizeType sizeNative = data.getSize();
    // ComLogger only writes 16-bit sizes to save space
    // on disk:
    FW_ASSERT(sizeNative < 65536, static_cast<FwAssertArgType>(sizeNative));
    U16 size = sizeNative & 0xFFFF;

    // Close the file if it will be too big:
    if (OPEN == this->m_fileMode) {
        U32 projectedByteCount = this->m_byteCount + size;
        if (this->m_storeBufferLength) {
            projectedByteCount += static_cast<U32>(sizeof(size));
        }
        if (projectedByteCount > this->m_maxFileSize) {
            this->closeFile();
        }
    }

    // Open the file if it there is not one open:
    if (CLOSED == this->m_fileMode) {
        this->openFile();
    }

    // Write to the file if it is open:
    if (OPEN == this->m_fileMode) {
        this->writeComBufferToFile(data, size);
    }
}

void ComLogger ::CloseFile_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
    this->closeFile();
    this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}

void ComLogger ::pingIn_handler(const FwIndexType portNum, U32 key) {
    // return key
    this->pingOut_out(0, key);
}

void ComLogger ::openFile() {
    FW_ASSERT(CLOSED == this->m_fileMode);

    if (!this->m_initialized) {
        this->log_WARNING_LO_FileNotInitialized();
        return;
    }

    // Create filename:
    Fw::Time timestamp = getTime();
    Fw::FormatStatus formatStatus = this->m_fileName.format(
        "%s_%" PRI_FwTimeBaseStoreType "_%" PRIu32 "_%06" PRIu32 ".com", this->m_filePrefix.toChar(),
        static_cast<FwTimeBaseStoreType>(timestamp.getTimeBase()), timestamp.getSeconds(), timestamp.getUSeconds());
    FW_ASSERT(formatStatus == Fw::FormatStatus::SUCCESS);
    this->m_hashFileName.format("%s%s", this->m_fileName.toChar(), Utils::Hash::getFileExtensionString());
    FW_ASSERT(formatStatus == Fw::FormatStatus::SUCCESS);

    Os::File::Status ret = m_file.open(this->m_fileName.toChar(), Os::File::OPEN_WRITE);
    if (Os::File::OP_OK != ret) {
        if (!this->m_openErrorOccurred) {  // throttle this event, otherwise a positive
                                           // feedback event loop can occur!
            this->log_WARNING_HI_FileOpenError(ret, this->m_fileName);
        }
        this->m_openErrorOccurred = true;
    } else {
        // Reset event throttle:
        this->m_openErrorOccurred = false;

        // Reset byte count:
        this->m_byteCount = 0;

        // Set mode:
        this->m_fileMode = OPEN;
    }
}

void ComLogger ::closeFile() {
    if (OPEN == this->m_fileMode) {
        // Close file:
        this->m_file.close();

        // Write out the hash file to disk:
        this->writeHashFile();

        // Update mode:
        this->m_fileMode = CLOSED;

        // Send event:
        this->log_DIAGNOSTIC_FileClosed(this->m_fileName);
    }
}

void ComLogger ::writeComBufferToFile(Fw::ComBuffer& data, U16 size) {
    if (this->m_storeBufferLength) {
        U8 buffer[sizeof(size)];
        Fw::SerialBuffer serialLength(&buffer[0], sizeof(size));
        serialLength.serializeFrom(size);
        if (this->writeToFile(serialLength.getBuffAddr(), static_cast<U16>(serialLength.getSize()))) {
            this->m_byteCount += static_cast<U32>(serialLength.getSize());
        } else {
            return;
        }
    }

    // Write buffer to file:
    if (this->writeToFile(data.getBuffAddr(), size)) {
        this->m_byteCount += size;
    }
}

bool ComLogger ::writeToFile(void* data, U16 length) {
    FwSizeType size = length;
    Os::File::Status ret = m_file.write(reinterpret_cast<const U8*>(data), size);
    if ((Os::File::OP_OK != ret) || (size != length)) {
        if (!this->m_writeErrorOccurred) {  // throttle this event, otherwise a positive
                                            // feedback event loop can occur!
            this->log_WARNING_HI_FileWriteError(ret, static_cast<U32>(size), length, this->m_fileName);
        }
        this->m_writeErrorOccurred = true;
        return false;
    }

    this->m_writeErrorOccurred = false;
    return true;
}

void ComLogger ::writeHashFile() {
    Os::ValidateFile::Status validateStatus;
    validateStatus = Os::ValidateFile::createValidation(this->m_fileName.toChar(), this->m_hashFileName.toChar());
    if (Os::ValidateFile::VALIDATION_OK != validateStatus) {
        this->log_WARNING_LO_FileValidationError(this->m_fileName, this->m_hashFileName, validateStatus);
    }
}
}  // namespace Svc
